/*
 * Copyright (C) 2011, Pavel Samko <bulldozerbsg@gmail.com>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the "Pavel Samko" nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

#include "VCNet.h"

#if VC_PLATFORM_WIN
#pragma comment(lib, "wsock32.lib")
#endif

//bool operator == (const VC_SOCKET_ADDR_IN & addr1, const VC_SOCKET_ADDR_IN & addr2)
//{
//	return (memcmp(&addr1, &addr2, sizeof(VC_SOCKET_ADDR_IN)) == 0);
//}

//bool operator < (const VC_SOCKET_ADDR_IN & addr1, const VC_SOCKET_ADDR_IN & addr2)
//{
//	if (memcmp(&addr1, &addr2, sizeof(VC_SOCKET_ADDR_IN)) > 0 )
//		return true;
//	return false;
//}

bool VCNetInit()
{
	bool result = true;
#if VC_PLATFORM_WIN
	WSADATA ws;
	if (WSAStartup(MAKEWORD(1, 1), &ws) == SOCKET_ERROR)
	{
		result = false;
	}
#endif
	return result;
}

void VCNetDeinit()
{
#if VC_PLATFORM_WIN
	WSACleanup();
#endif
}

VC_SOCKET VCNetSocketTCP()
{
	VC_SOCKET result = VC_SOCKET_INVALID;
	result = socket(PF_INET, SOCK_STREAM, 0);
#if VC_PLATFORM_MACOSX
	if (result != VC_SOCKET_INVALID)
	{
		int set = 1;
		setsockopt(result, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
	}
#endif
	return result;
}

void VCNetClose(VC_SOCKET s)
{
	if (s == VC_SOCKET_INVALID)
	{
		return;
	}
#if VC_PLATFORM_WIN
	closesocket(s);
#else
	close(s);
#endif
}

bool VCNetSetNonBlock(VC_SOCKET s, bool nonblock)
{
	bool result = true;
#if VC_PLATFORM_WIN
	if (s != VC_SOCKET_INVALID)
	{
		u_long noblockmode = nonblock? 1 : 0;
		if (ioctlsocket(s, FIONBIO, &noblockmode) == VC_SOCKET_ERROR)
		{
			result = false;
		}
	}
#else
	if (s != VC_SOCKET_INVALID)
	{
		long fl = fcntl(s, F_GETFL);
		if (fl < 0)
		{
			result = false;
		} else
		{
			if (nonblock)
			{
				fl |= O_NONBLOCK;
			} else
			{
				fl &= ~O_NONBLOCK;
			}

			if (fcntl(s, F_SETFL, fl) < 0)
			{
				result = false;;
			}
		}
	}
#endif
	return result;
}

bool VCNetGetSockName(VC_SOCKET s, VC_SOCKET_ADDR_IN * addr)
{
	bool result = true;
	socklen_t addr_size = sizeof(VC_SOCKET_ADDR_IN);
	if (getsockname(s, (VC_SOCKET_ADDR *)addr, (socklen_t *)&addr_size) == VC_SOCKET_ERROR)
	{
		result = false;
	}
	return result;
}

bool VCNetBind(VC_SOCKET s, const VC_SOCKET_ADDR_IN * addr)
{
	bool result = true;
	if (bind(s, (VC_SOCKET_ADDR *)addr, sizeof(VC_SOCKET_ADDR_IN)) == VC_SOCKET_ERROR)
	{
		result = false;
	}
	if (!VCNetSetNonBlock(s))
	{
		result = false;
	}
	return result;
}

bool VCNetConnect(VC_SOCKET s, const VC_SOCKET_ADDR_IN * addr)
{
	bool result = true;
	if (connect(s, (VC_SOCKET_ADDR *)addr, sizeof(VC_SOCKET_ADDR_IN)) == VC_SOCKET_ERROR)
	{
		result = false;
	}
	if (!VCNetSetNonBlock(s))
	{
		result = false;
	}
	return result;
}

bool VCNetListen(VC_SOCKET s, int max_conn)
{
	bool result = true;
	if (listen(s, max_conn) == VC_SOCKET_ERROR)
	{
		result = false;
	}
	return result;
}

VC_SOCKET VCNetAccept(VC_SOCKET s, VC_SOCKET_ADDR_IN * addr)
{
	socklen_t addr_size = sizeof(VC_SOCKET_ADDR_IN);
	VC_SOCKET result = accept(s, (VC_SOCKET_ADDR *)addr, &addr_size);
	if (!VCNetSetNonBlock(result))
	{
		VCNetClose(result);
		result = VC_SOCKET_INVALID;
	}
	return result;
}

bool VCNetSend(VC_SOCKET s, void * buff, size_t * buff_size)
{
	bool result = true;
	int flags = 0;
#if VC_PLATFORM_LINUX
	flags |= MSG_NOSIGNAL;
#endif
	int send_size = send(s, (const char *)buff, *buff_size, flags);
	if (send_size > 0)
	{
		*buff_size = send_size;
	} else
	if (send_size == 0)
	{
		*buff_size = 0;
		result = false;
	} else
	if (send_size == VC_SOCKET_ERROR)
	{
#if VC_PLATFORM_WIN
		int error = WSAGetLastError();
		if (error == WSAEWOULDBLOCK)
		{
			*buff_size = 0;
		} else
#else
		if (errno == EAGAIN)
		{
			*buff_size = 0;
		} else
#endif
		{
			*buff_size = 0;
			result = false;
		}
	}
	return result;
}

bool VCNetRecv(VC_SOCKET s, void * buff, size_t * buff_size)
{
	bool result = true;
	int flags = 0;
#if VC_PLATFORM_LINUX
	flags |= MSG_NOSIGNAL;
#endif
	int recv_size = recv(s, (char *)buff, *buff_size, flags);
	if (recv_size > 0)
	{
		*buff_size = recv_size;
	} else
	if (recv_size == 0)
	{
		*buff_size = 0;
		result = false;
	} else
	if (recv_size == VC_SOCKET_ERROR)
	{
#if VC_PLATFORM_WIN
		int error = WSAGetLastError();
		if (error == WSAEWOULDBLOCK)
		{
			*buff_size = 0;
		} else
#else
		if (errno == EAGAIN)
		{
			*buff_size = 0;
		} else
#endif
		{
			*buff_size = 0;
			result = false;
		}
	}
	return result;
}

unsigned short VCNetHTONS(unsigned short x)
{
	return htons(x);
}

unsigned short VCNetNTOHS(unsigned short x)
{
	return ntohs(x);
}

unsigned long VCNetHTONL(unsigned long x)
{
	return htonl(x);
}

unsigned long VCNetNTOHL(unsigned long x)
{
	return ntohl(x);
}

bool VCNetInitAddrIn(VC_SOCKET_ADDR_IN * addr, const char * addr_str, unsigned short port)
{
	bool result = false;
	if (addr)
	{
		addr->sin_family = AF_INET;
		addr->sin_port = VCNetHTONS(port);
		if (addr_str == NULL)
		{
			addr->sin_addr.s_addr = INADDR_ANY;
		} else
		{
			addr->sin_addr.s_addr = inet_addr(addr_str);
		}
		memset(addr->sin_zero, 0, sizeof(addr->sin_zero));
		result = true;
	}
	return result;
}

const char * VCNetGetIp(const VC_SOCKET_ADDR_IN * addr)
{
	char * result = NULL;
	if (addr)
	{
#if VC_PLATFORM_WIN
		result = inet_ntoa(addr->sin_addr);
#else
		static char addr_s[16];
		inet_ntop(AF_INET, &addr->sin_addr, addr_s, 16);
		result = addr_s;
#endif
	}
	return result;
}

unsigned short VCNetGetPort(const VC_SOCKET_ADDR_IN * addr)
{
	unsigned short result = 0;
	if (addr)
	{
		result = VCNetNTOHS(addr->sin_port);
	}
	return result;
}

unsigned short VCNetGetPort(VC_SOCKET s)
{
	VC_SOCKET_ADDR_IN saddr;
	socklen_t saddr_len = sizeof(saddr);
	if (!getsockname(s, (VC_SOCKET_ADDR *)&saddr, &saddr_len))
		return VCNetNTOHS(saddr.sin_port);
	else
		return 0;
}

